package main

import "fmt"

/*
1、动态语言中数组和列表初始化后可以在添加元素，在静态语言中则不可以，Go中采用了折中方案推出slice切片(相当于动态数组)----底层也是数组
2、切片里的元素值可以重复
*/

func main() {
	/*
		一、切片的定义（var name []Type）
		切片(slice)长度处置空，底层表示为可以往里面添加元素的不定长数组，和长度为[...]的不定长数组有区别-----初始化后是否可以在添加元素
		切片初始化的时候也可能预设长度，未赋值的下标位置的元素为空，超过长度的需要用append等函数添加
	*/
	//1、定义数组
	var str1 []string
	fmt.Println(str1)
	//采用全局函数append(切片,需要添加的元素)给切片添加元素，并且需要赋值给原来的切片
	str1 = append(str1, "go")
	str1 = append(str1, "java")
	str1 = append(str1, "python")
	fmt.Println(str1) //打印[go java python]
	//一样的通过下标可以获取某个元素
	fmt.Println(str1[1]) //打印java

	/*
		二、切片初始化的三种方式（从数组直接创建、使用string{}、make）

	*/
	//1、从数组直接创建切片slice
	str2 := [5]string{"a", "b", "c", "d", "e"}
	str3 := str2[0:4] //将下标0到3的元素创建为切片，左闭右开，类python的用法
	fmt.Println(str3)

	//2、采用string来初始化一定长度切片
	str4 := []string{"a", "b", "c", "d", "e"}
	fmt.Printf("%T\n", str4) //字符串类型的切片[]string

	//3、make([]Type, 长度, 容量)函数----预先创建一定容量(空间)，但每个元素为空的切片、
	// 这里的容量表示提前向内存申请一定容量空间来存一定长度的slice，初始化的时候长度可以小于容量
	//容量大于初始化长度是为了后面添加元素的时候不用申请新的内存地址空间来存储
	//一般使用的时候我们没有写容量，是写长度，当容量不够的时候，append  就需要开劈新的地址空间，影响性能
	str5 := make([]int, 3)
	fmt.Printf("%T\n", str5)
	str5[0] = 1
	str5[1] = 2
	str5[2] = 3
	// str5[3] = 4   注意由于开辟的大小为3，所以超容长度的不能采用这种方式了
	str5 = append(str5, 4) //超过预设长度的采用append(slice,value)来添加
	fmt.Println(str5)

	/*
		三、切片元素的访问
	*/
	str6 := []string{"go", "java", "python", "vue", "html", "c"}
	// 1、访问单个元素
	fmt.Println(str6[4]) //获取下标为4的元素html
	// 2、访问多个元素---[start:end]
	//按范围获取多个元素，左闭右开，并且start和end也可置空
	//当[start:]表示从start到结尾的所有元素
	//当[:end]表示包含下标为end-1之前的所有元素
	fmt.Println(str6[:4])  //[go java python vue]
	fmt.Println(str6[3:])  //[vue html c]
	fmt.Println(str6[:])   //表示所有的元素
	fmt.Println(str6[1:4]) // [java python vue]

	/*
		四、slice添加元素及slice的合并(append函数的用法)
	*/
	// 1、在固定长度的切片中添加单个元素----append()
	str7 := []string{"小红", "小李", "小姜"}
	str7 = append(str7, "小饶")
	fmt.Println(str7) //[小红 小李 小姜 小饶]

	// 2、添加多个元素----append()
	//append函数有返回值，需要有变量承接，并且elems...表示这个后的参数可以是多个
	str7 = append(str7, "小刘", "小赵", "小马")
	fmt.Println(str7) //[小红 小李 小姜 小饶 小刘 小赵 小马]

	// 3、省略号合并两个数组append(slice1, slice2...)
	str8 := []string{"a", "b", "c"}
	str9 := []string{"k", "m", "n"}
	//这里省略号...表示将str9切片打散为每个元素添加进行，不加...的话str9表示一个整体不是字符串类型，类型不对应会报错
	str8 = append(str8, str9...)
	fmt.Println(str8)

	// 4、将切片的部分元素进行合并append(slice1, slice2[start:end]...)
	str10 := []string{"a", "b", "c"}
	str11 := []string{"k", "m", "n"}
	str12 := append(str10, str11[:2]...)
	fmt.Println(str12) //[a b c k m]
	str13 := append(str10[1:], str11[:2]...)
	fmt.Println(str13) //[b c k m]
	//当然切片的合并通过for循环也是可以实现的

	/*
		五、slice切片的拷贝
		深度拷贝: 拷贝出的变量指向新的内存地址，和原来的变量两个互不影响，修改任意一个不会影响其他的
		浅拷贝： 复制的变量指向原来的内存地址，修改任意一个，另外一个也会被影响
	*/
	str14 := []string{"a", "b", "c"}
	str15 := str14    //拷贝方式1		浅拷贝
	str16 := str14[:] //拷贝方式2  浅拷贝
	fmt.Println("拷贝方式1：", str15)
	fmt.Println("拷贝方式2：", str16)
	// 如上两种方式str15和str16只是指向了同一个内存空间位置，存储str14的
	var str17 []string
	copy(str17, str14) //copy(目标slice, 源slice)   源slice---拷贝----》目标slice
	fmt.Println(str17) //打印结果为[]，因为需要定义切片没有提前开辟空间

	//拷贝方式3
	var str18 = make([]string, len(str14)) //一般采用copy（）拷贝需要提前开辟不小于源头slice的定长目标slice
	copy(str18, str14)                     //深度拷贝 copy(目标slice, 源slice)   源slice---拷贝----》目标slice
	fmt.Println("拷贝方式3：", str18)

	fmt.Println("*************改变源头slice的值看三种方式的不同*************")
	str14[1] = "k"               //改变源头slice第二个元素为k，源头slice由 [a b c]----》[a k c]
	fmt.Println("拷贝方式1：", str15) //打印结果: 拷贝方式1： [a k c]
	fmt.Println("拷贝方式2：", str16) //打印结果: 拷贝方式1： [a k c]
	fmt.Println("拷贝方式3：", str18) //拷贝方式3： [a b c]
	//由于可见copy是将源头slice拷贝到另外一个内存空间，两者互不干扰，
	//而另外两种只是指向源头slice，会因为源头slice改变而改变
}
